/*
 * MyBank.cn Inc.
 * Copyright (c) 2015-2021 All Rights Reserved.
 */
package me.caosh.util.factory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import java.text.MessageFormat;
import java.util.Map;
import java.util.Objects;

/**
 * 实现工厂组件自动注册的BeanPostProcessor
 *
 * @author mujiang.csh
 * @version v0.1, created at 2021/8/7 17:55
 */
@Component
@SuppressWarnings({"rawtypes", "unchecked"})
public class FactoryBeanPostProcessor implements BeanPostProcessor, Ordered {
    private static final Logger logger = LoggerFactory.getLogger(FactoryBeanPostProcessor.class);

    /**
     * 在所有其他Processor处理之后再将bean注册到工厂，保证各种动态代理生效；可定制
     */
    private int order = Ordered.LOWEST_PRECEDENCE;

    @Autowired
    private ListableBeanFactory listableBeanFactory;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        FactoryComponent factoryComponentDef = bean.getClass().getAnnotation(FactoryComponent.class);

        if (factoryComponentDef == null) {
            return bean;
        }

        Class<?> componentInterface = factoryComponentDef.componentInterface();
        logger.info("自动注册工厂组件: {}, {}, {}", componentInterface.getSimpleName(), factoryComponentDef.componentKey(),
                beanName);

        if (!componentInterface.isAssignableFrom(bean.getClass())) {
            throw new IllegalArgumentException(MessageFormat.format("Bean [{0}] is not an instance of {1}",
                    beanName, componentInterface));
        }

        BaseComponentFactory<Object> factory = (BaseComponentFactory<Object>)
                findCorrespondingComponentFactory(componentInterface);
        factory.add(factoryComponentDef.componentKey(), bean);

        return bean;
    }

    private ComponentFactory<?> findCorrespondingComponentFactory(Class<?> componentInterface) {
        Map<String, BaseComponentFactory> allComponentFactories = listableBeanFactory.getBeansOfType(
                BaseComponentFactory.class);
        return allComponentFactories.values()
                .stream()
                .filter(f -> matchComponentFactory(f, componentInterface))
                .findAny()
                .orElseThrow(() -> new IllegalArgumentException(MessageFormat.format(
                        "Component factory for [{0}] is not found", componentInterface.getSimpleName())));
    }

    private boolean matchComponentFactory(BaseComponentFactory<?> componentFactory, Class<?> componentInterface) {
        Factory factory = componentFactory.getClass().getAnnotation(Factory.class);
        Objects.requireNonNull(factory, "ComponentFactory class must be annotated with @Factory");
        return factory.value() == componentInterface;
    }

    @Override
    public int getOrder() {
        return order;
    }

    public void setOrder(int order) {
        this.order = order;
    }
}